﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenMcdf;
using System.IO;

namespace OracleOleRecovery
{
    public class WordRecovery:FileRecovery
    {
        /// <summary>
        /// recoverFile recovers the original Word file from an OLE version of it.
        /// </summary>
        /// <param name="inFile">location of the OLE file to recover</param>
        /// <param name="outFolder">folder for the extracted Word file</param>
        /// <param name="outFile">file name for the extracted Word file</param>
        /// <param name="logFile">log file stream to write out errors</param>
        /// <returns>A value of 1 is returned if a file is extracted and a 0 otherwise.
        /// </returns>
        public static int recoverFile(string inFile, string outFolder, string outFile, StreamWriter logFileStream)
        {
            Byte[] decodedWord = null;
            int retVal = 0;
            bool packageStream = false;
            Byte[] wordFile = null;
            bool multiStream = false;
            int streamsFound = 0;


            CompoundFile sourceDoc = new CompoundFile(inFile);         //  open up the document using an instance of the compound file
            CompoundFile recoveredDoc = new CompoundFile();           //  create a new compound file to store

            //  loop through all streams, 
            VisitedEntryAction va = delegate(CFItem item)
            {
                if (item.IsStream)      //  check if the item is a stream
                {
                    CFStream theStream = (CFStream)item;        //  cast it appropriately

                    //  check the stream name for one of the six relevant word document streams 
                    if (theStream.Name.ToLower().Contains("data") | theStream.Name.ToLower().Contains("1table") |
                        theStream.Name.ToLower().Contains("compobj") | theStream.Name.ToLower().Contains("worddocument") |
                        theStream.Name.ToLower().Contains("summaryinformation") | theStream.Name.ToLower().Contains("documentsummaryinformation"))
                    {
                        string streamName = theStream.Name;

                        //  we only need one instance of the 6 streams 
                        if (theStream != null & streamsFound < 6)
                        {
                            try
                            {
                                recoveredDoc.RootStorage.AddStream(streamName);     //  add a new stream to the recovered document
                                CFStream newStream = recoveredDoc.RootStorage.GetStream(streamName);        //  get a reference to the new stream
                                newStream.AppendData(theStream.GetData());          //  copy the data from the source stream to the new stream
                                multiStream = true;
                                streamsFound++;
                            }
                            catch (CFDuplicatedItemException)
                            {
                                //  ignore since we already have this stream added
                            }
                            catch (ArgumentOutOfRangeException)
                            {
                                //  this error occurs infrequently and is due to internal bug in the OpenMCDF library
                                logFileStream.WriteLine("\"" + outFile + "\",\"Failed Argument Out of Range Exception on stream" + streamName + "\",\"\",\"\"");
                            }
                        }

                    }
                    else if (theStream.Name.ToLower().Contains("package"))
                    {
                        Byte[] encodedWord = theStream.GetData();

                        // convert to a string so we can search for Word.Document.8
                        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
                        string wordText = enc.GetString(encodedWord);

                        int startOfFilePosition = 0;
                        int endOfFilePosition = wordText.LastIndexOf("Word.Document.8");

                        if (endOfFilePosition > startOfFilePosition)
                        {

                            int arrayLength = endOfFilePosition - startOfFilePosition + "Word.Document.8".Length + 5;

                            decodedWord = new Byte[arrayLength];
                            Array.Copy(encodedWord, startOfFilePosition, decodedWord, 0, arrayLength);
                            wordFile = decodedWord;
                            packageStream = true;
                        }

                    }
                }

            };

            //  visit all items in the compound file recursively
            sourceDoc.RootStorage.VisitEntries(va, true);


            //  write out file 
            if (packageStream)
            {
                if (wordFile != null)
                {
                    File.WriteAllBytes(outFolder + outFile, wordFile);
                    retVal = 1;
                }
                else
                {
                    logFileStream.WriteLine("\"" + outFile + "\",\"Failed\",\"\",\"\"");
                }
            }
            else if (multiStream)
            {
                if (recoveredDoc != null)
                {
                    recoveredDoc.Save(outFolder + outFile);
                    retVal = 1;
                }
                else
                {
                    logFileStream.WriteLine("\"" + outFile + "\",\"Failed\",\"\",\"\"");
                }
            }
            else
            {
                logFileStream.WriteLine("\"" + outFile + "\",\"Stream Not Found\",\"\",\"\"");
            }

            return retVal;

        }
        
        /// <summary>
        /// recoverFile recovers the original Word file from an OLE version of it.
        /// </summary>
        /// <param name="inFile">location of the OLE file to recover</param>
        /// <param name="outFolder">folder for the extracted Word file</param>
        /// <param name="outFile">file name for the extracted Word file</param>
        /// <param name="logFile">log file to write out errors</param>
        /// <returns>A value of 1 is returned if a file is extracted and a 0 otherwise.
        /// </returns>
        public static int recoverFile(string inFile, string outFolder, string outFile, string logFile)
        {
            StreamWriter logFileStream = new StreamWriter(logFile);

            return recoverFile(inFile, outFolder, outFile, logFile);
        }




        /// <summary>
        /// Recover original files from database retrieved OLE versions
        /// </summary>
        /// <param name="sourceFoulder">root folder where the documents are located, with a binaries folder which contains the  
        /// documents
        /// </param>
        public static void recoverFiles(string sourceFoulder, string outFolder)
        {
            int counter = 0;
            int missed = 0;
            int recoveredCount = 0;

            sourceFoulder = fixFolderName(sourceFoulder);
            outFolder = fixFolderName(outFolder);
            
            DateTime startTime = DateTime.Now;

            //  create a log file
            StreamWriter logFile = new StreamWriter(outFolder + "log.csv");
            string[] files = Directory.GetFiles(sourceFoulder);

            logFile.WriteLine("Total files: " + files.Length);

            //  loop through all files in the folder
            foreach (string file in files)
            {
                if (file.ToLower().Substring(file.Length - 3) == "doc")
                {
                    if (recoverFile(file, outFolder, Path.GetFileName(file), logFile) == 0)
                    {
                        missed++;
                    }
                    else
                    {
                        recoveredCount++;
                    }

                }
                counter++;
            }


            DateTime endTime = DateTime.Now;

            logFile.WriteLine("Total files processed: " + counter);
            logFile.WriteLine("Total Word files recovered: " + recoveredCount);
            logFile.WriteLine("Total files missed: " + missed);
            logFile.WriteLine("Start Time: {0}", startTime);
            logFile.WriteLine("Finish Time: {0}", endTime);
            logFile.WriteLine("Elapsed Time: {0}", endTime - startTime);

            logFile.Close();
        }

    }
}
